#include "data_manager.h"

#include <iostream>

#include "generated/version.h"
#include "utility.h"

namespace autodrive {
namespace perception {
DataManager::DataManager()
    : is_init_{false},
      is_loading_{false},
      data_frames_ptr_{nullptr},
      lastest_data_frame_index_{-1},
      is_dispose_{false} {}

DataManager::~DataManager() { Close(); }

DataManager& DataManager::GetInstance() noexcept {
  static DataManager instance;
  return instance;
}

///@brief init
///@param settings DataManagerSettings
void DataManager::Init(DataManagerSettings const& settings) noexcept {
  auto initFunc{[this]() {
    if (data_frames_ptr_ == nullptr) {
      data_frames_ptr_ = std::make_shared<DataFrameArray>();
    }
    show_info();
  }};
  std::call_once(init_flag_, initFunc);
  settings_ = settings;
  is_init_ = true;
}

/// @brief load all dataframe circlely
void DataManager::Load() noexcept(false) {
  std::lock_guard<std::mutex> lock(mutex_);
  if (!IsInit()) {
    throw std::runtime_error("data manager is not init");
  }

  auto loadFunc{[](DataManager* dataManagerPtr) {
    if (dataManagerPtr == nullptr) {
      std::cout << "[" << to_yellow("Error") << "]"
                << " dataManagerPtr is nullptr" << std::endl;
      return;
    }

    while (true) {
      try {
        if (dataManagerPtr->is_dispose_) {
          std::cout << "[" << to_yellow("Warr") << "]"
                    << " Data Manager is command to stop......" << std::endl;
          break;
        }

        auto const& loadDataFrameSettings{
            dataManagerPtr->settings_.load_data_frame_settings};
        auto const& cycle{dataManagerPtr->settings_.cycle};

        for (auto& ds : *dataManagerPtr->data_scene_array_ptr_) {
          std::cout << "[" << to_green("Info") << "]"
                    << " switch data scene: " << ds.Path() << std::endl;
          auto const& keys{ds.DataFrameKeys()};
          for (auto const& key : *keys) {
            Timer t;
            auto index{(dataManagerPtr->lastest_data_frame_index_ + 1) %
                       kMaxDataFrameNum};

            auto& dataFrame{dataManagerPtr->data_frames_ptr_->at(index)};
            ds.LoadDataFrame(loadDataFrameSettings, key, dataFrame);
            dataManagerPtr->lastest_data_frame_index_ = index;
            dataManagerPtr->latest_data_scene_ptr_ = &ds;

            auto const& timeCost{t.elapsed() / (float)1000};
            if (cycle > timeCost) {
              dataManagerPtr->msleep(
                  static_cast<std::int32_t>(cycle - timeCost));
            }

            if (dataManagerPtr->is_dispose_) {
              std::cout << "[" << to_yellow("Warr") << "]"
                        << " Data Manager is command to stop......"
                        << std::endl;
              break;
            }
          }

          if (dataManagerPtr->is_dispose_) {
            std::cout << "[" << to_yellow("Warr") << "]"
                      << " Data Manager is command to stop......" << std::endl;
            break;
          }
        }
      } catch (const std::exception& e) {
        std::cout << "[" << to_red("Error") << "]"
                  << " Data manager load data frame error, " << e.what()
                  << std::endl;
      } catch (...) {
        std::cout << "[" << to_red("Error") << "]"
                  << " Data manager load data frame error, unexpected"
                  << std::endl;
      }
    }
  }};

  if (!IsLoading()) {
    if (data_scene_array_ptr_ == nullptr) {
      data_scene_array_ptr_ = std::make_shared<DataSceneArray>();
    }
    auto const& dataSceneNum{settings_.data_scene_paths.size()};
    data_scene_array_ptr_->resize(dataSceneNum);

    for (std::int32_t i{0}; i < dataSceneNum; ++i) {
      auto& ds{data_scene_array_ptr_->at(i)};
      auto const& path{settings_.data_scene_paths.at(i)};
      ds.Init(path);
    }

    thread_load_ = std::thread(loadFunc, this);
  }
  is_loading_ = true;
}

std::pair<DataScene::DataFrame&, DataScene&> DataManager::LastestDataFrame()
    const noexcept(false) {
  if (!IsInit()) {
    throw std::runtime_error("data manager is not init");
  }

  if (!IsLoading()) {
    throw std::runtime_error("data manager is not loading");
  }

  if (latest_data_scene_ptr_ == nullptr) {
    throw std::runtime_error("latest data scene is loading");
  }

  if (data_frames_ptr_ == nullptr) {
    throw std::runtime_error("data_frames_ptr_ is nullptr");
  }

  if (lastest_data_frame_index_ < 0) {
    throw std::runtime_error("data frame run out");
  }

  auto index{0};
  if (lastest_data_frame_index_ > 0) {
    index = lastest_data_frame_index_;
  }

  return std::make_pair(std::ref(data_frames_ptr_->at(index)),
                        std::ref(*latest_data_scene_ptr_));
}

void DataManager::Close() noexcept {
  is_dispose_ = true;

  if (IsLoading()) {
    if (thread_load_.joinable()) {
      thread_load_.join();
    }
  }

  is_init_ = false;
  is_loading_ = false;
}

void DataManager::show_info() noexcept {
  std::cout << "************************************" << std::endl;
  std::cout << "*          Dataset Module          *" << std::endl;
  std::cout << "*                                  *" << std::endl;
  std::cout << "* Version: " << PROJECT_VERSION << "                  *"
            << std::endl;
  std::cout << "*                                  *" << std::endl;
  std::cout << "* Compile time: " << COMPILE_TIME << "   *" << std::endl;
  std::cout << "*                                  *" << std::endl;
  std::cout << "************************************" << std::endl;
}
}  // namespace perception
}  // namespace autodrive